(function($) {
  'use strict';

  $.jqPaginator = function(el, options) {
    if (!(this instanceof $.jqPaginator)) {
      return new $.jqPaginator(el, options);
    }

    const self = this;

    self.$container = $(el);

    self.$container.data('jqPaginator', self);

    self.init = function() {

      if (options.first || options.prev || options.next || options.last || options.page) {
        options = $.extend({}, {
          first: '',
          prev: '',
          next: '',
          last: '',
          page: '',
        }, options);
      }

      self.options = $.extend({}, $.jqPaginator.defaultOptions, options);

      self.verify();

      self.extendJquery();

      self.render();

      self.fireEvent(this.options.currentPage, 'init');
    };

    self.verify = function() {
      const opts = self.options;

      if (!self.isNumber(opts.totalPages)) {
        throw new Error('[jqPaginator] type error: totalPages');
      }

      if (!self.isNumber(opts.totalCounts)) {
        throw new Error('[jqPaginator] type error: totalCounts');
      }

      if (!self.isNumber(opts.pageSize)) {
        throw new Error('[jqPaginator] type error: pageSize');
      }

      if (!self.isNumber(opts.currentPage)) {
        throw new Error('[jqPaginator] type error: currentPage');
      }

      if (!self.isNumber(opts.visiblePages)) {
        throw new Error('[jqPaginator] type error: visiblePages');
      }

      if (!opts.totalPages && !opts.totalCounts) {
        throw new Error('[jqPaginator] totalCounts or totalPages is required');
      }

      if (!opts.totalPages && !opts.totalCounts) {
        throw new Error('[jqPaginator] totalCounts or totalPages is required');
      }

      if (!opts.totalPages && opts.totalCounts && !opts.pageSize) {
        throw new Error('[jqPaginator] pageSize is required');
      }

      if (opts.totalCounts && opts.pageSize) {
        opts.totalPages = Math.ceil(opts.totalCounts / opts.pageSize);
      }

      if (opts.currentPage < 1 || opts.currentPage > opts.totalPages) {
        throw new Error('[jqPaginator] currentPage is incorrect');
      }

      if (opts.totalPages < 1) {
        throw new Error('[jqPaginator] totalPages cannot be less currentPage');
      }
    };

    self.extendJquery = function() {
      $.fn.jqPaginatorHTML = function(s) {
        return s ? this.before(s).remove() : $('<p>').append(this.eq(0).clone()).html();
      };
    };

    self.render = function() {
      self.renderHtml();
      self.setStatus();
      self.bindEvents();
    };

    self.renderHtml = function() {
      const html = [];

      const pages = self.getPages();
      for (let i = 0, j = pages.length; i < j; i++) {
        html.push(self.buildItem('page', pages[i]));
      }

      self.isEnable('prev') && html.unshift(self.buildItem('prev', self.options.currentPage - 1));
      self.isEnable('first') && html.unshift(self.buildItem('first', 1));
      self.isEnable('statistics') && html.unshift(self.buildItem('statistics'));
      self.isEnable('next') && html.push(self.buildItem('next', self.options.currentPage + 1));
      self.isEnable('last') && html.push(self.buildItem('last', self.options.totalPages));

      if (self.options.wrapper) {
        self.$container.html($(self.options.wrapper).html(html.join('')).jqPaginatorHTML());
      } else {
        self.$container.html(html.join(''));
      }
    };

    self.buildItem = function(type, pageData) {
      const html = self.options[type]
        .replace(/{{page}}/g, pageData)
        .replace(/{{totalPages}}/g, self.options.totalPages)
        .replace(/{{totalCounts}}/g, self.options.totalCounts);

      return $(html).attr({
        'jp-role': type,
        'jp-data': pageData,
      }).jqPaginatorHTML();
    };

    self.setStatus = function() {
      const options = self.options;

      if (!self.isEnable('first') || options.currentPage === 1) {
        $('[jp-role=first]', self.$container).addClass(options.disableClass);
      }
      if (!self.isEnable('prev') || options.currentPage === 1) {
        $('[jp-role=prev]', self.$container).addClass(options.disableClass);
      }
      if (!self.isEnable('next') || options.currentPage >= options.totalPages) {
        $('[jp-role=next]', self.$container).addClass(options.disableClass);
      }
      if (!self.isEnable('last') || options.currentPage >= options.totalPages) {
        $('[jp-role=last]', self.$container).addClass(options.disableClass);
      }

      $('[jp-role=page]', self.$container).removeClass(options.activeClass);
      $('[jp-role=page][jp-data=' + options.currentPage + ']', self.$container).addClass(options.activeClass);
    };

    self.getPages = function() {
      let pages = [],
        visiblePages = self.options.visiblePages,
        currentPage = self.options.currentPage,
        totalPages = self.options.totalPages;

      if (visiblePages > totalPages) {
        visiblePages = totalPages;
      }

      const half = Math.floor(visiblePages / 2);
      let start = currentPage - half + 1 - visiblePages % 2;
      let end = currentPage + half;

      if (start < 1) {
        start = 1;
        end = visiblePages;
      }
      if (end > totalPages) {
        end = totalPages;
        start = 1 + totalPages - visiblePages;
      }

      let itPage = start;
      while (itPage <= end) {
        pages.push(itPage);
        itPage++;
      }

      return pages;
    };

    self.isNumber = function(value) {
      const type = typeof value;
      return type === 'number' || type === 'undefined';
    };

    self.isEnable = function(type) {
      return self.options[type] && typeof self.options[type] === 'string';
    };

    self.switchPage = function(pageIndex) {
      self.options.currentPage = pageIndex;
      self.render();
    };

    self.fireEvent = function(pageIndex, type) {
      return (typeof self.options.onPageChange !== 'function') || (self.options.onPageChange(pageIndex, type) !== false);
    };

    self.callMethod = function(method, options) {
      switch (method) {
        case 'option':
          self.options = $.extend({}, self.options, options);
          self.verify();
          self.render();
          break;
        case 'destroy':
          self.$container.empty();
          self.$container.removeData('jqPaginator');
          break;
        default :
          throw new Error('[jqPaginator] method "' + method + '" does not exist');
      }

      return self.$container;
    };

    self.bindEvents = function() {
      const opts = self.options;

      self.$container.off();
      self.$container.on('click', '[jp-role]', function() {
        const $el = $(this);
        if ($el.hasClass(opts.disableClass) || $el.hasClass(opts.activeClass)) {
          return;
        }

        const pageIndex = +$el.attr('jp-data');
        if (self.fireEvent(pageIndex, 'change')) {
          self.switchPage(pageIndex);
        }
      });
    };

    self.init();

    return self.$container;
  };

  $.jqPaginator.defaultOptions = {
    wrapper: '',
    first: '<li class="first"><a href="javascript:;">第一页</a></li>',
    prev: '<li class="prev"><a href="javascript:;">上一页</a></li>',
    next: '<li class="next"><a href="javascript:;">下一页</a></li>',
    last: '<li class="last"><a href="javascript:;">最后一页</a></li>',
    page: '<li class="page"><a href="javascript:;">{{page}}</a></li>',
    totalPages: 0,
    totalCounts: 0,
    pageSize: 0,
    currentPage: 1,
    visiblePages: 7,
    disableClass: 'disabled',
    activeClass: 'active',
    onPageChange: null,
  };

  $.fn.jqPaginator = function() {
    let self = this,
      args = Array.prototype.slice.call(arguments);

    if (typeof args[0] === 'string') {
      const $instance = $(self).data('jqPaginator');
      if (!$instance) {
        throw new Error('[jqPaginator] the element is not instantiated');
      } else {
        return $instance.callMethod(args[0], args[1]);
      }
    } else {
      return new $.jqPaginator(this, args[0]);
    }
  };

})(jQuery);
